/* 
 * File:   ipd_equalization.cpp
 * Author: dima
 * 
 * Created on 6 января 2015 г., 13:16
 */
#include <cstring>
#include "ipd_equalization.h"
#include "ipd_img.h"
#include "ipd_exception.h"

namespace ipd
{

ipd_equalization::ipd_equalization(ipd_img &img, int step_color) : image(img), step(step_color)
{
    count_intervals = ((image.get_max_color() - image.get_min_color()) / (double) step) + 1;
    count_points = count_intervals + 1;

    histogram = NULL;
    X = NULL; //X-axis values
    Y = NULL; //Y-axis values
    K = NULL;
    B = NULL;
}

ipd_equalization::~ipd_equalization()
{
    delete_histogram(); //delete histogram
    delete_curve();
    delete_KB();
}

void ipd_equalization::convert_image(ipd_img &convertible_image)//convert Image
{
    create_histogram(); //create histogram
    create_curve(); //create curve
    int width = image.get_width();
    int height = image.get_height();
    unsigned char min, max;
    min = image.get_min_color();
    max = image.get_max_color();
    convertible_image.init(width, height, image.get_color_type());
    int interval;
    switch (image.get_color_type())
    {
    case tp_mono:
    {
        try
        {
            create_equation_monochrome();
            unsigned char color_pixel_old, color_pixel_new;
            for (int j = 0; j < height; j++)
            {
                for (int i = 0; i < width; i++)
                {
                    color_pixel_old = *image.get_color(i, j);
                    interval = (color_pixel_old / (double) step);
                    color_pixel_new = (K[interval]*(double) color_pixel_old + B[interval]) * max; // normalized equation of the line from 0 to 255
                    color_pixel_new = color_pixel_new < min ? min : color_pixel_new; //normalization
                    color_pixel_new = color_pixel_new > max ? max : color_pixel_new; //normalization
                    convertible_image.set_color(i, j, &color_pixel_new);
                }
            }
        }
        catch (ipd::ipd_exception &except)
        {
            throw ipd_exception(except.what(), except.in_file());
        }
        break;
    }
    case tp_RGB:
    {
        try
        {
            unsigned char *color_pixel_old;
            unsigned char ch;
            unsigned char color_pixel_new[sz_RGB];
            create_equation_RGB();
            for (int j = 0; j < height; j++)
            {
                for (int i = 0; i < width; i++)
                {
                    color_pixel_old = image.get_color(i, j);
                    for (int p = 0; p < sz_RGB; p++)
                    {
                        ch = *(color_pixel_old + p);
                        interval = (ch / (double) step);
                        color_pixel_new[p] = (K[interval * sz_RGB + p]*(double) ch + B[interval * sz_RGB + p]) * max; // normalized equation of the line from 0 to 255
                        color_pixel_new[p] = color_pixel_new[p] < min ? min : color_pixel_new[p]; // normalization
                        color_pixel_new[p] = color_pixel_new[p] > max ? max : color_pixel_new[p]; // normalization
                    }
                    convertible_image.set_color(i, j, color_pixel_new);
                }
            }
        }
        catch (ipd::ipd_exception &except)
        {
            throw ipd_exception(except.what(), except.in_file());
        }
        break;
    }
    default:
    {
        throw ipd_exception(std::string("Image model is not supported (Supports RGB and monochrome image)"), std::string("ipd_equalization"));
    }
    }

}

ipd_equalization::ipd_equalization(const ipd_equalization& orig) : image(orig.image), step(orig.step)
{

}

void ipd_equalization::create_curve()//create the transformation curve
{
    delete_curve();
    int i, j;
    X = new double [count_points];
    for (i = 0; i < count_points; i++)
    {
        X[i] = step * i;
        X[i] = X[i] > image.get_max_color() ? image.get_max_color() : X[i];
    }

    switch (image.get_color_type())
    {
    case tp_mono:
    {
        Y = new double [count_points];
        for (i = 0; i < count_points; i++)
        {
            Y[i] = 0;
            for (j = 0; j < i; j++)
                Y[i] += histogram[j];
        }
        break;
    }
    case tp_RGB:
    {

        Y = new double [count_points * sz_RGB];
        for (i = 0; i < count_points; i++)
        {
            Y[i * sz_RGB] = 0;
            Y[i * sz_RGB + 1] = 0;
            Y[i * sz_RGB + 2] = 0;
            for (j = 0; j < i; j++)
            {
                Y[i * sz_RGB] += histogram[j * sz_RGB];
                Y[i * sz_RGB + 1] += histogram[j * sz_RGB + 1];
                Y[i * sz_RGB + 2] += histogram[j * sz_RGB + 2];
            }
        }
        break;
    }
    default:
    {
        throw ipd_exception(std::string("Image model is not supported (Supports RGB and monochrome image)"), std::string("ipd_equalization"));
    }
    }
}

void ipd_equalization::create_histogram()
{
    int width = image.get_width();
    int height = image.get_height();
    int sz_image = width*height;
    double x_1, x_2;
    double y;
    unsigned char *color;
    delete_histogram();
    int sz_histogram = get_size_histogram();
    histogram = new double [sz_histogram];
    for (int k = 1; k < count_points; k++)
    {
        x_2 = step * k;
        x_1 = step * (k - 1);
        x_2 = x_2 > image.get_max_color() ? image.get_max_color() : x_2;
        x_1 = x_1 > image.get_max_color() ? image.get_max_color() : x_1;
        switch (image.get_color_type())
        {
        case tp_mono:
        {
            int count = 0;
            for (int j = 0; j < height; j++)
            {
                for (int i = 0; i < width; i++)
                {
                    color = image.get_color(i, j);
                    if (*color >= x_1 && *color < x_2)
                        count++;
                }
            }
            y = count / (double) sz_image;
            // data for histograms
            histogram[k - 1] = y;
            break;
        }
        case tp_RGB:
        {
            int count_r = 0;
            int count_g = 0;
            int count_b = 0;
            for (int j = 0; j < height; j++)
            {
                for (int i = 0; i < width; i++)
                {
                    color = image.get_color(i, j);
                    if (*color >= x_1 && *color < x_2)
                        count_r++;
                    if (*(color + 1) >= x_1 && *(color + 1) < x_2)
                        count_g++;
                    if (*(color + 2) >= x_1 && *(color + 2) < x_2)
                        count_b++;

                }
            }
            y = count_r / (double) sz_image;
            histogram[(k - 1) * sz_RGB] = y; // data for histograms
            y = count_g / (double) sz_image;
            histogram[(k - 1) * sz_RGB + 1] = y; // data for histograms
            y = count_b / (double) sz_image;
            histogram[(k - 1) * sz_RGB + 2] = y; // data for histograms
            break;
        }
        }
    }
}

int ipd_equalization::get_size_histogram()//get the size of the histogram for each image type
{
    switch (image.get_color_type())
    {
    case tp_mono:
    {
        return count_points;
    }
    case tp_RGB:
    {
        return count_points * sz_RGB;
    }
    default:
    {
        throw ipd_exception(std::string("Image model is not supported (Supports RGB and monochrome image)"), std::string("ipd_equalization"));
    }
    }
}

void ipd_equalization::get_XY(int index, double &x, double *&y)//get the X and Y value of the curve
{
    int sz = get_size_histogram();
    if (index >= sz || index < 0)
    {
        throw ipd_exception(std::string("The index value is set incorrectly"), std::string("ipd_equalization"));
    }
    x = X[index];
    switch (image.get_color_type())
    {
    case tp_RGB:
    {
        y = Y + (index * sz_RGB);
        break;
    }
    case tp_mono:
    {
        y = Y + index;
        break;
    }
    }
}

void ipd_equalization::get_histogram(int index, double *&h, double &x1, double &x2)//get the value of the histogram
{
    int sz = get_size_histogram();
    if (index >= sz || index < 0)
    {
        throw ipd_exception(std::string("The index value is set incorrectly"), std::string("ipd_equalization"));
    }
    x2 = step * index;
    x1 = step * (index - 1);
    x2 = x2 > image.get_max_color() ? image.get_max_color() : x2;
    x1 = x1 > image.get_max_color() ? image.get_max_color() : x1;
    switch (image.get_color_type())
    {
    case tp_RGB:
    {
        h = histogram + (index * sz_RGB);
        break;
    }
    case tp_mono:
    {
        h = histogram + index;
        break;
    }

    }
}

void ipd_equalization::copy_histogram(double *orig_histogram)//copy histogram
{
    delete_histogram();
    if (orig_histogram == NULL)
        throw ipd_exception(std::string("Histogram are incorrect"), std::string("ipd_equalization"));
    int sz_histogram = get_size_histogram();
    histogram = new double [sz_histogram];
    std::memcpy(histogram, orig_histogram, sz_histogram);
}

void ipd_equalization::copy_XY(double *orig_X, double *orig_Y)//X-axis values and Y-axis values
{
    delete_curve();
    if (orig_X == NULL || orig_Y == NULL)
        throw ipd_exception(std::string("X or Y are incorrect"), std::string("ipd_equalization"));
    int sz_histogram = get_size_histogram();
    X = new double [count_points];
    std::memcpy(X, orig_X, count_points);
    Y = new double [sz_histogram];
    std::memcpy(Y, orig_Y, sz_histogram);

}

void ipd_equalization::delete_histogram()//delete histogram
{
    if (histogram != NULL)
        delete []histogram;
    histogram = NULL;
}

void ipd_equalization::delete_curve() //delete curve
{
    if (X != NULL)
        delete []X;
    X = NULL;
    if (Y != NULL)
        delete []Y;
    Y = NULL;
}

void ipd_equalization::delete_KB() //delete coefficients of the line
{
    if (K != NULL)
        delete []K;
    K = NULL;
    if (B != NULL)
        delete []B;
    B = NULL;
}

void ipd_equalization::create_equation_monochrome()//Creating an equation for direct interpolation values
{
    delete_KB();
    K = new double [count_intervals];
    B = new double [count_intervals];
    for (int i = 0; i < count_intervals; i++)
    {
        K[i] = (Y[i + 1] - Y[i]) / (X[i + 1] - X[i]);
        B[i] = (Y[i] * X[i + 1] - Y[i + 1] * X[i]) / (X[i + 1] - X[i]);
    }
}

void ipd_equalization::create_equation_RGB()//Creating an equation for direct interpolation values
{
    delete_KB();
    K = new double [count_intervals * sz_RGB];
    B = new double [count_intervals * sz_RGB];
    for (int i = 0; i < count_intervals; i++)
    {
        for (int j = 0; j < sz_RGB; j++)
        {
            K[i * sz_RGB + j] = (Y[(i + 1) * sz_RGB + j] - Y[i * sz_RGB + j]) / (X[i + 1] - X[i]);
            B[i * sz_RGB + j] = (Y[i * sz_RGB + j] * X[i + 1] - Y[(i + 1) * sz_RGB + j] * X[i]) / (X[i + 1] - X[i]);
        }
    }
}

}
